local floor = math.floor
local char = string.char
local format = string.format
local open = io.open
local print = print

local f = open(arg[1], "rb")
local s = f:read "*a"
f:close()
local i = 0

local f = arg[2] and open(arg[2], "wb")

local function readByte()
	local v = s:byte(i+1)
	i = i + 1
	return v
end

local function readShort()
	local v = s:byte(i+2) * 0x100 + s:byte(i+1)
	i = i + 2
	return v
end

local function readInt()
	local v = s:byte(i+4) * 0x1000000 + s:byte(i+3) * 0x10000 + s:byte(i+2) * 0x100 + s:byte(i+1)
	i = i + 4
	return v
end

local function readLong()
	local v = s:byte(i+4) * 0x1000000 + s:byte(i+3) * 0x10000 + s:byte(i+2) * 0x100 + s:byte(i+1)
	v = v + s:byte(i+8) * (2^56) + s:byte(i+7) * (2^48) + s:byte(i+6) * (2^40) + s:byte(i+5) * (2^32)
	i = i + 8
	return v
end

local function readStr()
	local n = readInt()
	local v = s:sub(i+1, i+n)
	i = i + n
	return v
end

local index = 0
local timeMs = 0
local lastPkg = ""
while i < #s do
	local pos = i
	io.write(format("%8X:", pos))
	local mark = readInt() -- 0x00: Capture pattern
	if mark ~= 0x5367674f then
		error "unmatched 'OggS' header"
	end
	local ver = readByte() -- 0x04: Version
	if ver ~= 0 then
		error "unmatched version=0"
	end
	local ht = readByte()  -- 0x05: Header type
	local hts = floor(ht / 2) % 2 == 1 and "B" or " "
	hts = hts .. (ht % 2 == 1 and "C" or " ")
	hts = hts .. (floor(ht / 4) % 2 == 1 and "E" or " ")
	local gp = readLong()  -- 0x06: Granule position
	local bsn = readInt()  -- 0x0E: Bitstream serial number
	local psn = readInt()  -- 0x12: Page sequence number
	local cs = readInt()   -- 0x16: Checksum
	local ps = readByte()  -- 0x1A: Page segments
	local tn, pn = 0, 0    -- 0x1B: page/packet length
	local nt, st = {}, {}
	for j = 1, ps do
		local sn = readByte() -- segment length
		pn = pn + sn
		tn = tn + sn
		if sn < 255 then
			nt[#nt + 1] = pn
			st[#st + 1] = format(" %3X", pn)
			pn, sn = 0, 0
		end
	end
	if pn ~= 0 then
		st[#st + 1] = "..."
	end
	for _, n in ipairs(nt) do
		if f and gp > 0 then
			local nn = #lastPkg + n
			f:write(char(floor(nn / 0x1000000)))
			f:write(char(floor(nn / 0x10000) % 256))
			f:write(char(floor(nn / 256) % 256))
			f:write(char(nn % 256))
			f:write("\0\0\0\0")
			if lastPkg ~= "" then
				f:write(lastPkg)
				lastPkg = ""
			end
			f:write(s:sub(i+1, i+n))
		end
		i = i + n
	end
	if pn ~= 0 then
		lastPkg = lastPkg .. s:sub(i+1, i+pn)
		i = i + pn
	else
		lastPkg = ""
	end
	print(format("%6d: [%4X] %s %16X %8X %8X [%2X:%4X] %s",
		index, i - pos, hts, gp, bsn, psn, ps, tn, table.concat(st)))
	if index == 0 then
		local j = i
		i = i - tn
		mark = readInt()
		if mark == 0x7375704f then -- 'Opus'
			mark = readInt()
			if mark == 0x64616548 then -- 'Head'
				print("                 version   : " .. readByte())
				print("                 channels  : " .. readByte())
				print("                 preSkip   : " .. readShort())
				print("                 sampleRate: " .. readInt())
				print("                 gain(dB)  : " .. readShort())
				print("                 mapping   : " .. readByte())
			end
		end
		i = j
	elseif index == 1 then
		local j = i
		i = i - tn
		mark = readInt()
		if mark == 0x7375704f then -- 'Opus'
			mark = readInt()
			if mark == 0x73676154 then -- 'Tags'
				print("                 0: " .. readStr())
				local n = readInt()
				for k = 1, n do
				print("                 " .. k .. ": " .. readStr())
				end
			end
		end
		i = j
	end
	index = index + 1
end
print "-----------------------------------------------------------------------"

if f then
	f:close()
end
